#include    "router.h"
#include    "../utils/logger.h"

extern "C" {
#include    <luajit.h>
#include    <lauxlib.h>
#include    <lualib.h>
}

#if defined(_WIN32)
#	include	<regex>
#	define	USE_CPP_REGEX	1
#else
#	include	<regex.h>
#	define	USE_CPP_REGEX	0
#endif

struct Rule {
    std::string method;
    std::string pattern;
    int         proc_ref;
#if USE_CPP_REGEX
    std::regex  check;
#else
    regex_t     check;
#endif
};

Router::~Router() {
    for (auto rule : _rules) {
    #if !USE_CPP_REGEX
        regfree(&rule->check);
    #endif
        delete rule;
    }
}

void Router::Prepare(struct lua_State * lua) {
    Router ** ptr = (Router **)(lua_newuserdata(lua, sizeof(Router *)));
	(*ptr) = this;
	luaL_getmetatable(lua, "__omni_router");
	lua_setmetatable(lua, -2);
    lua_setglobal(lua, "router");
}

int Router::Check(const std::string & url, const std::string & method, int & proc, std::vector<std::string> & matches) const {
    bool valid_url = false;
    bool valid_method = false;

#if USE_CPP_REGEX
    std::smatch captures;
    for (auto rule : _rules) {
        if (regex_match(url, captures, rule->check)) {
            valid_url = true;
            if (rule->method == "ANY" || rule->method == method) {
                valid_method = true;
                proc = rule->proc_ref;
                for (size_t i = 1; i < captures.size(); ++i) matches.push_back(captures[i].str());
                break;
            }
        }
    }
#else
    regmatch_t captures[16] = {0};
    for (auto rule : _rules) {
        size_t match_count = rule->check.re_nsub + 1;
        if (0 == regexec(&rule->check, url.c_str(), match_count, captures, 0)) {
            valid_url = true;
            if (rule->method == "ANY" || rule->method == method) {
                valid_method = true;
                proc = rule->proc_ref;
                for (size_t i = 1; i < match_count; ++i) matches.push_back(url.substr(captures[i].rm_so, captures[i].rm_eo - captures[i].rm_so));
                break;
            }
        }
    }
#endif

    if (!valid_url) return 404;
    if (!valid_method) return 405;
    return 200;
}

void Router::__AddRule(const std::string & method, const std::string & pattern, int lua_proc) {
    Rule * rule = new Rule;
    rule->method = method;
    rule->pattern = pattern;
    rule->proc_ref = lua_proc;

#if USE_CPP_REGEX
    try {
        rule->check = std::regex(pattern);
    } catch (...) {
        Logger::Instance().Error("Bad router pattern [%s] : %s", method.c_str(), pattern.c_str());
        delete rule;
        return;
    }    
#else
    if (0 != regcomp(&rule->check, pattern.c_str(), REG_EXTENDED | REG_NEWLINE)) {
        Logger::Instance().Error("Bad router pattern [%s] : %s", method.c_str(), pattern.c_str());
        delete rule;
        return;
    }    
#endif

    _rules.push_back(rule);
}